#ifdef _PAPI_

/* Papi headers */
# include "papi_hook.hpp"

/* Papi headers */
# include "papi.h"

/* Linux x86 headers */
# include <sched.h>

# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <inttypes.h>

const int PAPI_MAX_STR_LENGTH = 128;
int EventSet = PAPI_NULL;
int retval;
long long values[1];
long long all_values[128];
const char *all_names[128];
char descr[PAPI_MAX_STR_LENGTH];
PAPI_event_info_t evinfo;

const unsigned int eventlist[] = {
  PAPI_L1_DCM  ,
  // PAPI_L1_ICM  ,
  PAPI_L2_DCM  ,
  // PAPI_L2_ICM  ,
  // PAPI_L1_TCM  ,
  // PAPI_L2_TCM  ,
  PAPI_L3_TCM  ,
  PAPI_TLB_DM  ,
  // PAPI_TLB_IM  ,
  PAPI_L1_LDM  ,
  PAPI_L1_STM  ,
  PAPI_L2_STM  ,
  // PAPI_STL_ICY ,
  // PAPI_BR_UCN  ,
  PAPI_BR_CN   ,
  // PAPI_BR_TKN  ,
  // PAPI_BR_NTK  ,
  PAPI_BR_MSP  ,
  // PAPI_BR_PRC  ,
  PAPI_TOT_INS ,
  PAPI_FP_INS  ,
  PAPI_LD_INS  ,
  PAPI_SR_INS  ,
  PAPI_BR_INS  ,
  PAPI_TOT_CYC ,
  // PAPI_L2_DCH  ,
  // PAPI_L2_DCA  ,
  // PAPI_L3_DCA  ,
  // PAPI_L2_DCR  ,
  // PAPI_L3_DCR  ,
  // PAPI_L2_DCW  ,
  // PAPI_L3_DCW  ,
  // PAPI_L2_ICH  ,
  // PAPI_L2_ICA  ,
  // PAPI_L3_ICA  ,
  // PAPI_L2_ICR  ,
  // PAPI_L3_ICR  ,
  PAPI_L2_TCA  ,
  // PAPI_L3_TCA  ,
  // PAPI_L2_TCR  ,
  // PAPI_L3_TCR  ,
  // PAPI_L2_TCW  ,
  // PAPI_L3_TCW  ,
  PAPI_FDV_INS ,
  PAPI_FP_OPS  ,
  PAPI_VEC_SP  ,
  PAPI_VEC_DP  ,
  PAPI_REF_CYC,
  0
};

const char* eventnames[] = {
  "Level 1 data cache misses          : ",
  // "Level 1 instr cache misses         : ",
  "Level 2 data cache misses          : ",
  // "Level 2 instr cache misses         : ",
  // "Level 1 cache misses               : ",
  // "Level 2 cache misses               : ",
  "Level 3 cache misses               : ",
  "Data TLB misses                    : ",
  // "Instr TLB misses                   : ",
  "Level 1 load misses                : ",
  "Level 1 store misses               : ",
  "Level 2 store misses               : ",
  // "Cycles with no instr issue         : ",
  // "Unconditional branch instrs        : ",
  "Cond. branch instrs                : ",
  // "Cond. branch instrs taken          : ",
  // "Cond. branch instrs not taken      : ",
  // "Cond. branch instrs mispredicted   : ",
  "Cond. branch instrs correctly pred : ",
  "Instrs completed                   : ",
  "Floating point instrs              : ",
  "Load instrs                        : ",
  "Store instrs                       : ",
  "Branch instrs                      : ",
  "Total cycles                       : ",
  // "Level 2 data cache hits            : ",
  // "Level 2 data cache accesses        : ",
  // "Level 3 data cache accesses        : ",
  // "Level 2 data cache reads           : ",
  // "Level 3 data cache reads           : ",
  // "Level 2 data cache writes          : ",
  // "Level 3 data cache writes          : ",
  // "Level 2 instr cache hits           : ",
  // "Level 2 instr cache accesses       : ",
  // "Level 3 instr cache accesses       : ",
  // "Level 2 instr cache reads          : ",
  // "Level 3 instr cache reads          : ",
  "Level 2 total cache accesses       : ",
  // "Level 3 total cache accesses       : ",
  // "Level 2 total cache reads          : ",
  // "Level 3 total cache reads          : ",
  // "Level 2 total cache writes         : ",
  // "Level 3 total cache writes         : ",
  "Floating point divide instrs       : ",
  "Floating point operations          : ",
  "Single precision vector/SIMD instrs: ",
  "Double precision vector/SIMD instrs: ",
  "Reference clock cycles             : ",
  0
};

// const unsigned int eventlist[] = {
//     PAPI_FP_STAL,
//     // PAPI_MEM_SCY,
//     // PAPI_RES_STL,
//     0
// };

// const char* eventnames[] = {
//     "FP STALL:\t",
//     // "MEM STALL:\t",
//     // "TOTAL STALL:\t",
//     0
// };

template <typename T>
void PAPI<T>::papi_test_fail(char *file, int line, char *call, int retval) {
    char buf[128];

    memset(buf, '\0', sizeof(buf));
    if (retval != 0)
	fprintf(stdout,"%-40s FAILED\nLine # %d\n", file, line);
    else {
	fprintf(stdout,"%-40s SKIPPED\n", file);
	fprintf(stdout,"Line # %d\n", line);
    }
    if (retval == PAPI_ESYS) {
	sprintf(buf, "System error in %s", call);
	perror(buf);
    } else if (retval > 0) {
	fprintf(stdout,"Error: %s\n", call);
    } else if (retval == 0) {
	fprintf(stdout,"Error: %s\n", call);
    } else {
	char errstring[PAPI_MAX_STR_LEN];
	// PAPI_perror(retval, errstring, PAPI_MAX_STR_LEN);
	fprintf(stdout,"Error in %s: %s\n", call, errstring);
    }
    fprintf(stdout,"\n");
    if ( PAPI_is_initialized() ) PAPI_shutdown();
    exit(1);
}

template <typename T>
void PAPI<T>::papi_clean_cache() {
    long double* cache_cleaner =
	(long double*) malloc (150000 * sizeof (long double));
    int cache_iter;

    for (cache_iter = 0; cache_iter < 150000; ++cache_iter)
	cache_cleaner[cache_iter] = 3.14159 * cache_iter;
}

template <typename T>
void PAPI<T>::papi_check_failures_and_start(int evid) {
    if (PAPI_get_event_info(eventlist[evid], &evinfo) != PAPI_OK)
	papi_test_fail(__FILE__, __LINE__, "PAPI_get_event_info", retval);
  
  
    if ((retval = PAPI_start(EventSet)) != PAPI_OK)
	papi_test_fail(__FILE__, __LINE__, "PAPI_start", retval);
}

template <typename T>
void PAPI<T>::papi_init() {
    if ((retval = PAPI_library_init(PAPI_VER_CURRENT)) != PAPI_VER_CURRENT)
	papi_test_fail(__FILE__, __LINE__, "PAPI_library_init", retval);

    if ((retval = PAPI_create_eventset(&EventSet)) != PAPI_OK)
	papi_test_fail(__FILE__, __LINE__, "PAPI_create_eventset", retval);

    /* Use FIFO scheduler to limit OS interference. */
    struct sched_param schedParam;
    schedParam.sched_priority = 99;
    sched_setscheduler(0, SCHED_FIFO, &schedParam);
}


template <typename T>
void PAPI<T>::papi_loop(T &callable) {
  int evid, eviditer;
    papi_init();

    for (evid = 0; eventlist[evid] != 0; evid++) {
	printf("Working on eventid: %d\t%s\n", evid, eventnames[evid]);
	//PAPI_event_code_to_name(eventlist[evid], descr);
	if (PAPI_add_event(EventSet, eventlist[evid]) != PAPI_OK)
	    continue;
    
	papi_clean_cache();
	papi_check_failures_and_start(evid);

        // FUNCTION CALL TO MONITOR
        callable();

	// End PAPI Wrapper Loop
	if ((retval = PAPI_read(EventSet, &values[0])) != PAPI_OK)
	    papi_test_fail(__FILE__, __LINE__, "PAPI_read", retval);
	if ((retval = PAPI_stop(EventSet,NULL)) != PAPI_OK)
	    papi_test_fail(__FILE__, __LINE__, "PAPI_stop", retval);
   
	all_values[evid] = values[0];
	all_names[evid] = eventnames[evid];
   
	if ((retval = PAPI_remove_event(EventSet, eventlist[evid])) != PAPI_OK)
	    papi_test_fail(__FILE__, __LINE__, "PAPI_remove_event", retval);       
    } //End PAPI Wrapper loop body
        
        if ((retval = PAPI_destroy_eventset(&EventSet)) != PAPI_OK)
          papi_test_fail(__FILE__, __LINE__, "PAPI_destroy_eventset", retval);
        
    // Output measure results.
    printf ("\n\n\n****************\n****************\n****************\nValues:\n");
    for (eviditer = 0; eviditer < evid; ++eviditer)
	printf ("%s%32llu \n", all_names[eviditer], all_values[eviditer]);
    printf ("\n");
}

#include "constants.h"
#include "polyTask.hpp"
#include "base_uday_wrapper.hpp"
template class PAPI<PolyTask<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>>;
template class PAPI<Uday>;

#endif // _PAPI_
